﻿using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.AI;
using Random = System.Random;

namespace Gj
{
    public class TerrainService : GameService
    {
        public static TerrainService single;
        private NavMeshHit _navMeshHit;
        private NavMeshPath _navMeshPath;
        public Dictionary<MeshFilter, NavMeshBuildSource> NavMeshBuildSources = new();
        public List<Vector3> pointList;
        public Random random;
        public TerrainBase terrain;

        static TerrainService()
        {
            single = new TerrainService();
        }

        public void OnInit()
        {
            pointList = new List<Vector3>();
            _navMeshHit = new NavMeshHit();
            _navMeshPath = new NavMeshPath();
            random = new Random();
        }

        public void OnDestroy()
        {
            single = new TerrainService();
        }

        public void OnUpdate()
        {
            
        }

        public static TerrainBase Create(string map)
        {
            return ObjectService.single.GetObj("Terrains", map).GetComponent<TerrainBase>();
        }

        public void CreateTerrain(int x, int y, int r, int type, TerrainBase terrainBase)
        {
            terrain = terrainBase;
            terrain.Create(x, y, new Random(r), type, process =>
            {
                StatusService.single.mapProcess = process;
                if (process == 100) StatusService.single.mapEnd = true;
            });
        }

        public void CreateTerrain(int x, int y, int r, int type)
        {
            CreateTerrain(x, y, r, type, ObjectService.single.GetObj("Terrains", "Base").GetComponent<TerrainBase>());
        }

        public void CreateTerrain(string map)
        {
            terrain = ObjectService.single.GetObj("Terrains", map).GetComponent<TerrainBase>();
            terrain.Open(process =>
            {
                StatusService.single.mapProcess = process;
                StatusService.single.mapEnd = true;
            });
        }

        public Vector3 GetBirthPoint(int index)
        {
            var position = terrain.GetBirthPoint(index);
            return new Vector3(position.x, 0, position.z);
        }

        public Vector3 GetBirthPoint()
        {
            Vector3 point;
            do
            {
                point = terrain.GetRandomPoint(random);
            } while (IsCollisionRange(point));

            return point;
        }

        private bool IsCollisionRange(Vector3 point)
        {
            for (var i = 0; i < pointList.Count; i++)
                if (Vector3.Distance(pointList[i], point) <= 1)
                    return true;
            return false;
        }

        public void ApplyPoint(Vector3 point)
        {
            pointList.Add(point);
        }

        public void ReleasePoint(Vector3 point)
        {
            pointList.Remove(point);
        }

        public void AddNavMesh(MeshFilter filter)
        {
            if (!NavMeshBuildSources.ContainsKey(filter))
            {
                var s = new NavMeshBuildSource();
                s.shape = NavMeshBuildSourceShape.Mesh;
                s.sourceObject = filter.sharedMesh;
                s.transform = filter.transform.localToWorldMatrix;
                s.area = 0;
                NavMeshBuildSources.Add(filter, s);
            }

            terrain.AddNavMesh(NavMeshBuildSources[filter]);
        }

        public void RemoveNavMesh(MeshFilter filter)
        {
            if (NavMeshBuildSources.ContainsKey(filter)) terrain.RemoveNavMesh(NavMeshBuildSources[filter]);
        }

        public Vector3[] FindPosition(Vector3 currentPosition, Vector3 position)
        {
            if (NavMesh.CalculatePath(currentPosition, position, NavMesh.AllAreas, _navMeshPath))
            {
                var p = _navMeshPath.corners.Skip(1).ToArray();
                return p;
            }

            NavMeshHit hit;
            if (NavMesh.SamplePosition(position, out hit, 1.0f, NavMesh.AllAreas))
                return FindPosition(currentPosition, hit.position);
            return null;
        }

        public void FixPaths(ref Vector3[] p, float distance)
        {
            // 改变路径中点的位置，为了不贴边缘走。
            for (var i = 0; i < p.Length; i++)
            {
                var result = NavMesh.FindClosestEdge(p[i], out _navMeshHit, NavMesh.AllAreas);
                if (result && _navMeshHit.distance < distance)
                    p[i] = _navMeshHit.position + _navMeshHit.normal * distance;
            }
        }

        public Vector3 Raycast(Vector3 currentPosition, Vector3 direction, float distance)
        {
            var end = currentPosition + distance * direction;
            var result = NavMesh.Raycast(currentPosition, end, out _navMeshHit, NavMesh.AllAreas);
            return result ? _navMeshHit.position : end;
        }

        public Vector3 EdgePosition(Vector3 currentPosition, ref Vector3 direction, float distance)
        {
            var end = currentPosition + distance * direction;
            var result = NavMesh.Raycast(currentPosition, currentPosition + 10 * direction, out _navMeshHit,
                NavMesh.AllAreas);
            if (result)
            {
                end = _navMeshHit.position; // + _navMeshHit.normal * distance;
                if (_navMeshHit.distance <= distance)
                {
//                    Debug.LogError(currentPosition+" to "+ (_navMeshHit.position + _navMeshHit.normal * distance * 2));
                    var tmpPosition = _navMeshHit.position + _navMeshHit.normal * distance * 2;

                    var leftDirection = RotateRound(direction, Vector3.zero, Vector3.up, 1);
                    NavMesh.Raycast(tmpPosition, tmpPosition + 10 * leftDirection, out _navMeshHit, NavMesh.AllAreas);
                    var leftDistance = _navMeshHit.hit ? _navMeshHit.distance : 0;
                    var leftPosition = _navMeshHit.position; // + _navMeshHit.normal * distance;

                    var rightDirection = RotateRound(direction, Vector3.zero, Vector3.up, -1);
                    NavMesh.Raycast(tmpPosition, tmpPosition + 10 * rightDirection, out _navMeshHit, NavMesh.AllAreas);
                    var rightDistance = _navMeshHit.hit ? _navMeshHit.distance : 0;
                    var rightPosition = _navMeshHit.position; // + _navMeshHit.normal * distance;

//                    Debug.LogError("left:"+leftDistance+","+leftPosition+", right:"+rightDistance+","+rightPosition+", end:"+end);
                    if (leftDistance > rightDistance)
                    {
                        end = leftPosition;
                        direction = (leftPosition - currentPosition).normalized;
                    }
                    else if (leftDistance < rightDistance)
                    {
                        end = rightPosition;
                        direction = (rightPosition - currentPosition).normalized;
                    }
                }
            }

            return end;
        }

        public static Vector3 RotateRound(Vector3 position, Vector3 center, Vector3 axis, float angle)
        {
            var point = Quaternion.AngleAxis(angle, axis) * (position - center);
            var resultVec3 = center + point;
            return resultVec3;
        }
    }
}